﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Vowei.Core.Models;
using System.Text.RegularExpressions;
using Vowei.Core;
using Vowei.Data.Models;
using System.Data;
using System.Data.Common;
using System.Data.Objects;
using System.Diagnostics;
using Vowei.Data.Helpers;
using System.Reflection;

namespace Vowei.Data.Helpers
{
    public static class EntityHelpers
    {
        public static IQueryable<T> Search<T>(this IQueryable<T> table, FilterCriteria criteria) where T : Ticket
        {
            var result = table;
            if (criteria == null)
                return result;

            if (criteria.IsActive.HasValue && criteria.IsActive.Value)
                result = result.Active();
            else if (criteria.IsActive.HasValue && !criteria.IsActive.Value)
                result = result.Closed();

            if (criteria.User != null)
                result = result.My(criteria.User);

            if (criteria.ChangedAfter.HasValue)
                result = result.ChangedAfter(criteria.ChangedAfter.Value, true);

            if (criteria.ChangedBefore.HasValue)
                result = result.ChangedBefore(criteria.ChangedBefore.Value, true);

            result = result.Skip(criteria.IterBeginIndex);
            if (criteria.TakeItemsCount.HasValue)
                result = result.Top(criteria.TakeItemsCount.Value);

            return result;
        }

        public static IQueryable<T> ChangedBefore<T>(this IQueryable<T> table, DateTime date, bool inclusive) where T : class, ISupportDefaultProperties
        {
            if ( inclusive )
                return table.Where(r => r.LastModified <= date);
            else
                return table.Where(r => r.LastModified < date);
        }

        public static IQueryable<T> ChangedAfter<T>(this IQueryable<T> table, DateTime date, bool inclusive) where T : class, ISupportDefaultProperties
        {
            if (inclusive)
                return table.Where(r => r.LastModified >= date);
            else
                return table.Where(r => r.LastModified > date);
        }

        /// <summary>
        /// 返回用户的任务
        /// </summary>
        /// <typeparam name="T">从Ticket类型继承下来的类型列表</typeparam>
        /// <param name="timeline">任务数据库</param>
        /// <returns>所有用户的任务</returns>
        public static IQueryable<T> My<T>(this IQueryable<T> table, IUser user) where T : Ticket
        {
            var username = user.GetIdentifier();
            return table.Where(t => t.AssignedTo == username);
        }

        // TODO: 这是为了演示临时加的代码,请在解决了用户名和邮件地址匹配问题之后去掉下面这个函数
        public static IQueryable<T> MyByEmail<T>(this IQueryable<T> table, IUser user) where T : Ticket
        {
            var username = user.Email;
            return table.Where(t => t.AssignedTo == username);
        }

        /// <summary>
        /// 返回属于用户负责的项目列表
        /// </summary>
        /// <param name="timeline">项目列表数据库</param>
        /// <returns>用户负责的所有项目</returns>
        public static IQueryable<Project> My(this IQueryable<Project> table, IUser user)
        {
            var username = user.GetIdentifier();
            return table.Where(t => t.Owner == username);
        }

        /// <summary>
        /// 返回指定表里面处于激活状态的记录
        /// </summary>
        /// <typeparam name="T">需要查询的数据类型，比如任务、项目等</typeparam>
        /// <param name="timeline">任务、项目等数据表</param>
        /// <returns>返回所有激活状态的数据</returns>
        public static IQueryable<T> Active<T>(this IQueryable<T> table) where T : class, ISupportDefaultProperties
        {
            return table.Where(t => !t.ClosedDate.HasValue);
        }

        /// <summary>
        /// 返回指定表里面处于关闭状态的记录
        /// </summary>
        /// <typeparam name="T">需要查询的数据类型，比如任务、项目等</typeparam>
        /// <param name="timeline">任务、项目等数据表</param>
        /// <returns>返回所有关闭状态的数据</returns>
        public static IQueryable<T> Closed<T>(this IQueryable<T> table) where T : class, ISupportDefaultProperties
        {
            return table.Where(t => t.ClosedDate.HasValue);
        }

        /// <summary>
        /// 返回某个结果集的前面几条记录
        /// </summary>
        /// <typeparam name="T">需要查询的数据类型，比如任务、项目等</typeparam>
        /// <param name="timeline">任务、项目等数据表</param>
        /// <param name="number">返回记录的条目数</param>
        /// <returns>指定结果集的前几条记录</returns>
        public static IQueryable<T> Top<T>(this IQueryable<T> table, int number)
        {
            if (number < 0)
                throw new ArgumentOutOfRangeException("number should greater than or equal to zero");

            return table.Take(number);
        }

        private static Regex _userEmailReg = new Regex(@"\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b", RegexOptions.IgnoreCase);
        public static bool MatchUserEmail(string email)
        {
            if (string.IsNullOrEmpty(email))
                return false;
            else
                return _userEmailReg.IsMatch(email);
        }

        /// <summary>
        /// 获取一个任务或者需求的父级需求
        /// </summary>
        /// <param name="ticket">一个任务或者需求</param>
        /// <returns>
        /// 指定任务或者需求的父级需求，如果<paramref name="ticket"/>已经是
        /// 最上层的需求，那么返回null
        /// </returns>
        public static Requirement GetUpRequirement(this Ticket ticket, IVoweiContext context)
        {
            if (ticket == null)
                throw new NullReferenceException();

            // 最上层的需求,上面就是里程碑了
            return context.Requirements.Include("BelongsToMilestone").Query.SingleOrDefault(r => r.Children.Any(t => t.ID == ticket.ID));
        }

        /// <summary>
        /// 获取一个任务或者需求所属的最下级的里程碑
        /// </summary>
        /// <param name="ticket">一个任务或者需求</param>
        /// <returns>返回一个任务或者需求所属的最下级的里程碑</returns>
        public static Milestone GetBelongingMilestone(this Ticket ticket, IVoweiContext context)
        {
            if (ticket == null)
                throw new NullReferenceException();

            if (ticket is Requirement)
                return (ticket as Requirement).BelongsToMilestone;
            else
            {
                var req = ticket.GetUpRequirement(context);
                if (req == null)
                    throw new InvalidOperationException(
                        string.Format("工作任务[{0}-{1}],并没有跟任何里程碑或者项目关联起来!", ticket.Title, ticket.ID));
                else
                    return req.BelongsToMilestone;
            }
        }

        public static Project GetBelongingProject(this Ticket ticket, IVoweiContext context)
        {
            if (ticket == null)
                throw new NullReferenceException();

            var milestone = ticket.GetBelongingMilestone(context);
            return milestone.GetBelongingProject(context);
        }

        public static Project GetBelongingProject(this Milestone milestone, IVoweiContext context)
        {
            if (milestone == null)
                throw new NullReferenceException();

            while (!(milestone is Project))
            {
                milestone = context.Milestones.Find(milestone.BelongsToMilestoneId);
            }

            return milestone as Project;
        }

        public static string GetEntityTypeName(Type type)
        {
            var nsName = type.Namespace;

            return string.CompareOrdinal(nsName, "System.Data.Entity.DynamicProxies") == 0 ?
                type.BaseType.Name : type.Name;
        }

        public static bool IsEntityProxyType(Type type)
        {
            return string.CompareOrdinal(type.Namespace, "System.Data.Entity.DynamicProxies") == 0;
        }

        public static IQueryable<Milestone> AllMilestones(this Milestone root, IVoweiContext context)
        {
            return FullTreeImpl<Milestone, Milestone>((Milestone)root, (VoweiContext)context).AsQueryable();
        }

        public static IQueryable<Ticket> AllTickets(this Requirement root, IVoweiContext context)
        {
            return FullTreeImpl<Requirement, Ticket>((Requirement)root, (VoweiContext)context).AsQueryable();
        }
                
        public static IQueryable<Requirement> AllRequirements(this Milestone root, IVoweiContext context)
        {
            if (root == null)
                throw new NullReferenceException("root");
            if (context == null)
                throw new ArgumentNullException("context");

            var result = new List<Requirement>();
            // 添加上当前顶级里程碑中自己里面的任务列表
            var milestones = new List<Milestone>();
            milestones.Add(root);
            milestones.AddRange(root.AllMilestones(context));

            foreach (var milestone in milestones)
            {
                var reqs = context.Requirements.Query.Where(r => r.BelongsToMilestoneId == milestone.ID).ToArray();
                foreach (var req in reqs)
                    result.Add(req);
            }

            return result.Distinct().AsQueryable();
        }

        public static IQueryable<Requirement> LeafRequirements(this IQueryable<Requirement> tickets)
        {
            if (tickets == null)
                throw new NullReferenceException();

            return tickets.Where(r => r.Children.OfType<Requirement>().Count() == 0);
        }
                        
        public static IQueryable<Milestone> UnAssignedMilestone(this IVoweiContext context)
        {
            return context.Milestones.Query.Where(m => m.BelongsToMilestoneId == Guid.Empty);
        }

        public static IQueryable<Ticket> AllTickets(this Milestone root, IVoweiContext context)
        {
            if (root == null)
                throw new NullReferenceException("root");
            if (context == null)
                throw new ArgumentNullException("context");

            var result = new List<Ticket>();
            // 添加上当前顶级里程碑中自己里面的任务列表
            var milestones = new List<Milestone>();
            milestones.Add(root);
            milestones.AddRange(root.AllMilestones(context));

            foreach (var milestone in milestones)
            {
                // TODO: 当解决外键的映射问题以后，将下面的注释取消掉
                // var reqs = context.Requirements.Query.Where(r => r.BelongsToMilestone.ID == milestone.ID).ToArray();
                var reqs = context.Requirements.Query.Where(r => r.BelongsToMilestoneId == milestone.ID).ToArray();
                foreach (var req in reqs)
                {
                    result.AddRange(req.FullTreeImpl<Requirement, Ticket>((VoweiContext)context));
                    result.Add(req);
                }

                // TODO: 当解决外键的映射问题以后，将下面的注释取消掉
                // foreach (var task in context.Tasks.Query.Where(t => t.BelongsToMilestone.ID == milestone.ID))
                foreach (var task in context.Tasks.Query.Where(t => t.BelongsToMilestoneId == milestone.ID))
                    result.Add(task);
            }
            
            return result.Distinct().AsQueryable();
        }

        public static IQueryable<Task> AllTasks(this Milestone milestone, IVoweiContext context)
        {
            return milestone.AllTickets(context).OfType<Task>();
        }

        public static IQueryable<Task> AllTasks(this Requirement requirement, IVoweiContext context)
        {
            return requirement.AllTickets(context).OfType<Task>();
        }

        public static IQueryable<Requirement> AllRequirements(this Requirement requirement, IVoweiContext context)
        {
            return requirement.AllTickets(context).OfType<Requirement>();
        }

        public static Project FindProjectFor(Ticket ticket, IVoweiContext context)
        {
            ticket = context.Tickets.Include("BelongsToMilestone").Query.Single(t => t.ID == ticket.ID);
            var milestone = context.Milestones.Find(ticket.BelongsToMilestoneId);
            var project = milestone.GetBelongingProject(context);
            if (project == null)
            {
                throw new InvalidOperationException(
                    string.Format("任务:{0}在系统中保存了与里程碑或需求的层次关系，但是找不到对应的里程碑信息", ticket.ID));
            }

            return project;
        }

        public static T GenericGetObjectDetails<T>(Guid? id, IVoweiContext context) where T : class, INamedTable, new()
        {
            T objEditing = default(T);
            if (!id.HasValue)
                objEditing = new T();
            else
            {
                var table = context.Resolve<T>();
                var properties = typeof(T).GetProperties().OrderBy(p => p.Name);

                foreach (var property in properties)
                {
                    if (property.PropertyType.IsGenericType &&
                        (property.PropertyType.GetGenericTypeDefinition() == typeof(List<>)))
                        table = table.Include(property.Name);
                    if (property.Name.EndsWith("Id"))
                    {
                        var pname = property.Name.Substring(0, property.Name.Length - 2);
                        if ( properties.Any(p => p.Name == pname) )
                            table = table.Include(pname);
                    }
                }

                objEditing = table.Query.FirstOrDefault(o => o.ID == id.Value);

                if (objEditing == null)
                    throw new ArgumentException(string.Format("找不到ID为 {0} 的数据!", id));
            }

            return objEditing;
        }

        internal static List<U> FullTreeImpl<T, U>(this T root, VoweiContext context)
            where T : class, ITable, IContainer<U>
        {
            if (root == null)
                throw new NullReferenceException("root");
            if (context == null)
                throw new ArgumentNullException("context");

            var stack = new Stack<T>();
            stack.Push(root);
            var result = new List<U>();
            var table = context.Resolve<T>();

            while (stack.Count > 0)
            {
                var id = stack.Pop().ID;
                var iter = table.Include("Children").Query.First(t => t.ID == id);
                var children = iter.Children.ToArray();
                foreach (var child in children)
                {
                    if (child is T)
                        stack.Push(child as T);
                }

                result.AddRange(children);
            }

            return result;
        }

        // TODO: 这个函数应该写成一个存储过程
        public static bool CheckPermission(this IQueryable<Ace> acl, string[] roles, string permission)
        {
            if (acl == null)
                throw new NullReferenceException("acl");
            if (roles == null)
                throw new ArgumentNullException("roles");
            if (string.IsNullOrEmpty(permission))
                throw new ArgumentNullException("permission");
            if (roles.Length == 0)
                return false;

            foreach (var role in roles)
            {
                var aces = acl.Where(a => a.RoleName == role).ToArray();
                foreach (var ace in aces)
                {
                    foreach (var perm in ace.Permissions)
                    {
                        if (perm.Authorized(permission))
                            return true;
                    }
                }
            }

            return false;
        }

        // TODO: 将UnAssignedRequirements, UnAssignedTasks和TopRequirements合并成一个函数
        public static Task[] UnAssignedTasks(this Project project, IVoweiContext context)
        {
            if (project == null)
                throw new NullReferenceException();
            if (context == null)
                throw new ArgumentNullException("context");

            var milestones = project.AllMilestones(context).ToList();
            var tasks = milestones
                .Join(context.Tasks.Query,
                      m => m, r => r.BelongsToMilestone, (m, r) => r)
                .Where(r => !context.Requirements.Query.Any(req => req.Children.Any(c => c.ID == r.ID)));
            var projectTasks = context.Tasks.Query
                .Where(r => r.BelongsToMilestoneId == project.ID);
            var result = tasks.ToList();

            foreach (var r in projectTasks.ToArray())
            {
                if (context.Requirements.Query.Any(req => !req.Children.Any(c => c.ID == r.ID)))
                    result.Add(r);
            }

#if DEBUG
            if (context.Tasks.Query.Any(t => t.BelongsToMilestone == null))
                Trace.TraceError("在系统中存在有没有指定项目或者里程碑的任务！");
#endif

            return result.ToArray();
        }

        public static Requirement[] TopRequirements(this Project project, IVoweiContext context)
        {
            if (project == null)
                throw new NullReferenceException();
            if (context == null)
                throw new ArgumentNullException("context");

            var milestones = project.Children.ToList();
            milestones.Add(project);
            // TODO: The performance of ToList approach is bad, please make this a procedure or
            // database view
            //var topRequirements = milestones
            //    .Join(context.Requirements.Query,
            //          m => m, r => r.BelongsToMilestone, (m, r) => r)
            //    .Where(r => !context.Requirements.Query.Any(req => req.Children.Any(c => c.ID == r.ID)));
            var topRequirements = milestones
                  .Join(context.Requirements.Query,
                        m => m.ID, r => r.BelongsToMilestoneId, (m, r) => r)
                  .Where(r => !context.Requirements.Query.Any(req => req.Children.Any(c => c.ID == r.ID)));
            
            return topRequirements.ToArray();
        }

        private static Regex _userDomainNameReg = new Regex(@"\b[A-Z0-9._%+-]+\\[A-Z0-9.-]+\b", RegexOptions.IgnoreCase);
        public static bool MatchDomainUserName(string userName)
        {
            return _userDomainNameReg.IsMatch(userName);
        }

        public static void FixUserEmails(Employee[] employees)
        {
            foreach (var employee in employees)
            {
                if (string.IsNullOrEmpty(employee.Email))
                {
                    var parts = employee.UserName.Split('\\');
                    if (string.IsNullOrEmpty(employee.UserName))
                        throw new InvalidOperationException("输入的用户不能即没有指定电子邮件地址，也没有指定用户名!");

                    employee.Email = string.Format("{0}@{1}.com", parts[1], parts[0]);
                }
            }
        }
    }
}
